home *** CD-ROM | disk | FTP | other *** search
/ Info-Mac 3 / Info_Mac_1994-01.iso / Development / Information / Mac Programming Secrets 1.0.1 / Chapter 07 / Neat Stuff.c < prev    next >
C/C++ Source or Header  |  1992-05-19  |  37KB  |  1,089 lines

  1. #include "Standard Stuff.h"
  2. #include "Neat Stuff.h"
  3. #include "ZoomRect.h"
  4.  
  5. /*******************************************************************************
  6.  
  7.     Global variables
  8.  
  9. *******************************************************************************/
  10.  
  11. short        gUntitledWindowNumber = 0;    /*    Number for next new window. These
  12.                                             numbers start at zero and keep
  13.                                             going up; they are not recycled. */
  14.  
  15. WindowPtr    gFirstWindow;                /*    We keep track of our windows in
  16.                                             the order in which they were
  17.                                             created. This variable points to
  18.                                             the first (oldest) window. */
  19.  
  20.  
  21. /*    Used when matching up windows to their menu items, and vice-versa. */
  22.  
  23. short        gMenuItem;
  24. Boolean        gDoneCounting;
  25. WindowPtr    gFoundWindow;
  26. WindowPtr    gTargetWindow;
  27.  
  28.  
  29. /* Variables set up by ForEachWindowDo() and ForEachWindowPerScreenDo() */
  30.  
  31. short        gWindowNumber;                /*    Ordinal number of the current
  32.                                             window on the current monitor. */
  33.  
  34. short        gNumberOfWindows;            /*    Total number of windows on the
  35.                                             current monitor. */
  36.  
  37. GDHandle    gScreenDevice;                /*    Reference to the current monitor. */
  38.  
  39. Rect        gScreenRect;                /*    Bounds of the current monitor. Takes
  40.                                             into account the menubar if this
  41.                                             is the main monitor. */
  42.  
  43.  
  44. /* Variables set up and shared by tiling and stacking routines. */
  45.  
  46. short        gScreenWidth;                /*    Width of the current monitor. */
  47.  
  48. short        gScreenHeight;                /*    Height of the current monitor. */
  49.  
  50. short        gNewWindowWidth;            /*    When tiling or stacking, every
  51.                                             window is essentially the same
  52.                                             size. This variable holds the
  53.                                             window’s new width. */
  54.  
  55. short        gNewWindowHeight;            /*    Ditto, but for height. */
  56.  
  57.  
  58.  
  59. /*******************************************************************************
  60.  
  61.     DoNewWindow
  62.  
  63.     Create a window from a ‘WIND’ resource. If that succeeds, show the window
  64.     and add it to the Windows menu. When showing the window, using a zoom
  65.     effect that zooms the window from the Apple menu to its final position.
  66.  
  67.     By passing in NIL as the window pointer in the call to GetNewWindow, we
  68.     tell the system to create a window record on the heap for us. This means
  69.     that we must close this window with a call to DisposeWindow rather than
  70.     CloseWindow.
  71.  
  72. *******************************************************************************/
  73. void    DoNewWindow(void)
  74. {
  75.     WindowPtr    newWindow;
  76.     Str255        untitledTitle;
  77.     Str255        numberAsString;
  78.     Rect        rect1, rect2;
  79.  
  80.     newWindow = GetNewWindow(kNewWindowID, NIL, (WindowPtr) -1);
  81.     if (newWindow != NIL) {
  82.         GetIndString(untitledTitle, rMiscStrings, sUntitledTitle);
  83.         NumToString(++gUntitledWindowNumber, numberAsString);
  84.         CatenatePStrings(untitledTitle, numberAsString);
  85.         SetWTitle(newWindow, untitledTitle);
  86.  
  87.         SetRect(&rect1, 0, 0, 0, 0);
  88.         rect2 = GetWindowStructureRect(newWindow);
  89.         ZoomRect(rect1, rect2, kZoomOut);
  90.  
  91.         ShowWindow(newWindow);
  92.         AddWindowToMenu(newWindow);
  93.     }
  94. }
  95.  
  96.  
  97. /*******************************************************************************
  98.  
  99.     DoCloseWindow
  100.  
  101.     Remove the specified window from the Windows menu, and close it by calling
  102.     DisposeWindow. When closing the window, reverse the zooming effect we did
  103.     when opening the window.
  104.  
  105.     Note that we call DisposeWindow rather than CloseWindow. Because we passed
  106.     in NIL as the window record storage when we create the window, we
  107.     dynamically allocated the record in the application’s heap. To free up
  108.     that record, we must call DisposeWindow.
  109.  
  110. *******************************************************************************/
  111. void    DoCloseWindow(WindowPtr theWindow)
  112. {
  113.     Rect        rect1, rect2;
  114.  
  115.     SetRect(&rect1, 0, 0, 0, 0);
  116.     rect2 = GetWindowStructureRect(theWindow);
  117.  
  118.     RemoveWindowFromMenu(theWindow);
  119.     DisposeWindow(theWindow);
  120.  
  121.     ZoomRect(rect2, rect1, kZoomIn);
  122. }
  123.  
  124.  
  125. /*******************************************************************************
  126.  
  127.     DoActivateWindow
  128.  
  129.     When a window becomes active, it’s almost the same as saying that it is
  130.     becoming the frontmost window. Similarly, when a window is being
  131.     deactivated, it’s almost the same as saying that another window is coming
  132.     to the front. Therefore, when a window is activated or deactivated, we
  133.     adjust where the checkmark is placed in the Windows menu.
  134.  
  135. *******************************************************************************/
  136. void    DoActivateWindow(WindowPtr theWindow, Boolean becomingActive)
  137. {
  138.     short        menuItem;
  139.     MenuHandle    windowsMenu;
  140.  
  141.     windowsMenu = GetMHandle(mWindows);
  142.     menuItem = GetMenuItemForWindow(theWindow);
  143.     if (becomingActive)
  144.         SetItemMark(windowsMenu, menuItem, checkMark);
  145.     else
  146.         SetItemMark(windowsMenu, menuItem, noMark);
  147. }
  148.  
  149.  
  150. /*******************************************************************************
  151.  
  152.     DoZoomWindow
  153.  
  154.     This routine gets called when the user clicks in the zoom box of a window.
  155.     It’s our own special ZoomWindow procedure that zooms the window to the
  156.     screen that contains the majority of the window.
  157.  
  158.     DoZoomWindow works by getting three pertinent rectangles: the bounds of
  159.     the monitor the window is on, the bounds of the window’s frame (its
  160.     structure), and the bounds of the window’s content area. Using these
  161.     rectangles, we calculate the where we want the content rectangle of the
  162.     zoomed window to be. We take that resulting rectangle and jam it into a
  163.     structure that the default WDEF maintains that helps it zoom windows in
  164.     and out. Once all that is done, we call ZoomWindow. ZoomWindow will zoom
  165.     the window to the size and location we just calculated.
  166.  
  167.     Note that in these days of the ‘90’s, there is another step that you might
  168.     want to take if you want your application to get all those Ooh’s and Aah’s
  169.     from the customers. With monitors getting larger and larger, it’s a good
  170.     idea to zoom your window to be only as large as it needs to be. For
  171.     instance, if the user zooms a word processing document on a 19 inch
  172.     monitor, it would be nice to zoom the window’s width out to 8.5 inches
  173.     rather than the full 13 inches. We don’t do this here, but it’s something
  174.     for you to think about.
  175.  
  176. *******************************************************************************/
  177. void    DoZoomWindow(WindowPtr window, short zoomDir, Boolean front)
  178. {
  179.     Rect    contentRect, structureRect, deviceRect;
  180.  
  181.     /*    If there is the possibility of multiple gDevices, we must check
  182.         them to make sure we are zooming to the right display device. */
  183.  
  184.     if ((zoomDir == inZoomOut) && (gMac.hasColorQD)) {
  185.  
  186.         contentRect        = GetWindowContentRect(window);
  187.         structureRect    = GetWindowStructureRect(window);
  188.         deviceRect        = GetWindowDeviceRectNMB(window);
  189.  
  190.         deviceRect.left        += (contentRect.left - structureRect.left + 2);
  191.         deviceRect.top        += (contentRect.top - structureRect.top + 2);
  192.         deviceRect.right    -= (structureRect.right - contentRect.right + 2);
  193.         deviceRect.bottom    -= (structureRect.bottom - contentRect.bottom + 2);
  194.  
  195.         (*(WStateDataHandle)(((WindowPeek)window)->dataHandle))->stdState = deviceRect;
  196.     }
  197.  
  198.     ZoomWindow(window, zoomDir, front);
  199. }
  200.  
  201.  
  202. /*******************************************************************************
  203.  
  204.     DoTileWindows
  205.  
  206.     Call ForEachWindowPerScreenDo, which is a routine that allows us to
  207.     perform a specified operation on every window. Our task here is to tile
  208.     all the windows, making sure that each is the same size, but in
  209.     non-overlapping locations. In our TileSetup routine, we calculate the
  210.     windows’ size and save it off. In our TileTheWindow routine, we set each
  211.     window to that size, and position it in the right place.
  212.  
  213. *******************************************************************************/
  214. void    DoTileWindows(void)
  215. {
  216.     ForEachWindowPerScreenDo(TileSetup, TileTheWindow, NIL);
  217. }
  218.  
  219.  
  220. const short    kMinHeight            =    100;
  221. const short    kMinWidth            =    100;
  222. const short    kGapBetweenWindows    =    2;
  223.  
  224. short        pMaxWindowsPerRow;
  225. short        pMaxWindowsPerColumn;
  226. short        pNumberOfColumns;
  227. short        pNumberOfRows;
  228.  
  229. //
  230. //    This function is called once per monitor by ForEachWindowPerScreenDo.
  231. //    Based on the size of the screen and the number of windows that are to
  232. //    be tiled on the screen, we determine the number of rows and columns
  233. //    that we want to tile the windows into. We also determine the size
  234. //    each window on this screen should be.
  235. //
  236. void    TileSetup(void)
  237. {
  238.     if (gNumberOfWindows > 0) {
  239.         gScreenRect.top += kGapBetweenWindows;
  240.         gScreenRect.left += kGapBetweenWindows;
  241.  
  242.         gScreenWidth = gScreenRect.right - gScreenRect.left;
  243.         gScreenHeight = gScreenRect.bottom - gScreenRect.top;
  244.  
  245.         pMaxWindowsPerRow = gScreenWidth / kMinWidth;
  246.         pMaxWindowsPerColumn = gScreenHeight / kMinHeight;
  247.  
  248.         pNumberOfColumns = (gNumberOfWindows-1) / pMaxWindowsPerColumn + 1;
  249.         pNumberOfRows = (gNumberOfWindows-1) / pNumberOfColumns + 1;
  250.  
  251.         gNewWindowWidth = gScreenWidth / pNumberOfColumns;
  252.         gNewWindowHeight = gScreenHeight / pNumberOfRows;
  253.     }
  254. }
  255.  
  256. //
  257. //    Main window cruncher for tiling. This routine is called once for every
  258. //    window. Makes the given window the size we determined in TileSetUp, and
  259. //    place it in the appropriate row and column based on its ordering in the
  260. //    window list.
  261. //
  262. //    Note that our calculations are set up to determine the size and location
  263. //    of the windows _frame_, not its content rectangle (i.e., its portRect).
  264. //    However, MoveWindow and SizeWindow like to work with values that _do_
  265. //    affect the portRect. To solve this problem, we do the actual moving and
  266. //    resizing by creating a routine called SetWindowBounds. This utility
  267. //    routine translates the frame rectangle into the content rectangle for us
  268. //    before calling MoveWindow and SizeWindow.
  269. //
  270. void    TileTheWindow(WindowPtr theWindow)
  271. {
  272.     Rect    newBounds;
  273.     short    row, column;
  274.  
  275.     row = (gWindowNumber-1) / pNumberOfColumns;
  276.     column = (gWindowNumber-1) % pNumberOfColumns;
  277.  
  278.     newBounds.left        = gScreenRect.left + column * gNewWindowWidth;
  279.     newBounds.top        = gScreenRect.top + row * gNewWindowHeight;
  280.     newBounds.right        = newBounds.left + gNewWindowWidth - kGapBetweenWindows;
  281.     newBounds.bottom    = newBounds.top + gNewWindowHeight - kGapBetweenWindows;
  282.  
  283.     SetWindowBounds(theWindow, newBounds);
  284. }
  285.  
  286.  
  287. /*******************************************************************************
  288.  
  289.     DoStackWindows
  290.  
  291.     Call ForEachWindowPerScreenDo, which is a routine that allows us to
  292.     perform a specified operation on every window. Our task here is to stack
  293.     all the windows. In our StackSetup routine, we calculate the number of
  294.     diagonals we’ll have on the monitor we are tiling on. We also calculate an
  295.     initial window size, a size which gets smaller as subsequent windows get
  296.     stacked further down and to the right. In our TileTheWindow routine, we
  297.     set each window to the appropriate size and position.
  298.  
  299. *******************************************************************************/
  300. void        DoStackWindows(void)
  301. {
  302.     ForEachWindowPerScreenDo(StackSetup, StackTheWindow, NIL);
  303. }
  304.  
  305.  
  306. const short    kVerticalStagger    =    20;
  307. const short    kHorizontalStagger    =    10;
  308. const short    kWindowsPerDiagonal    =    8;
  309.  
  310. //
  311. //    This function is called once per monitor by ForEachWindowPerScreenDo.
  312. //    Its purpose is two-fold. First, it figures out how many diagonals of
  313. //    windows will be needed to stack all of the windows on the current screen.
  314. //    Right now, we permit only 8 windows to be stacked in a diagonal before we
  315. //    need to create a new diagonal of windows that’s move over to the right a
  316. //    little bit.
  317. //
  318. //    The second thing this function does is figure out a good initial size for
  319. //    the stacked windows. This size shrinks for windows that are stacked
  320. //    further down and to the right on the screen. The calculations for this
  321. //    shrinking are in the StackTheWindow function.
  322. //
  323. void        StackSetup(void)
  324. {
  325.     short    maxWindowsInDiagonal;
  326.     short    numberOfBottomWindows;
  327.  
  328.     InsetRect(&gScreenRect, kGapBetweenWindows, kGapBetweenWindows);
  329.  
  330.     // Find the height and width of this window
  331.  
  332.     gScreenWidth = gScreenRect.right - gScreenRect.left;
  333.     gScreenHeight = gScreenRect.bottom - gScreenRect.top;
  334.  
  335.     // Find the longest diagonal of windows we will be dealing
  336.     // with. This will either be kWindowsPerDiagonal or
  337.     // gNumberOfWindows, whichever is smaller.
  338.  
  339.     maxWindowsInDiagonal = gNumberOfWindows;
  340.     if (maxWindowsInDiagonal > kWindowsPerDiagonal)
  341.         maxWindowsInDiagonal = kWindowsPerDiagonal;
  342.  
  343.     // Find out how many windows will end up in the last
  344.     // position of a diagonal. This number is crucial in
  345.     // determining the horizontal span of all the windows,
  346.     // which is used in calculating the windows’ width.
  347.  
  348.     numberOfBottomWindows = (gNumberOfWindows / kWindowsPerDiagonal);
  349.     if (numberOfBottomWindows > 0)
  350.         --numberOfBottomWindows;
  351.  
  352.     // Figure out the size of the first window to be stacked.
  353.     // We start off with the the size of the entire screen and
  354.     // start trimming it back to allow for other windows. First,
  355.     // we trim back the width by an amount equal to the number
  356.     // of pixels that the rightmost window will be from the
  357.     // left edge of the screen. Next, we trim back the height so
  358.     // that we can accommodate the tallest diagonal we’ll be dealing
  359.     // with.
  360.  
  361.     gNewWindowWidth = gScreenWidth
  362.                         - (((maxWindowsInDiagonal - 1) + numberOfBottomWindows)
  363.                             * kHorizontalStagger);
  364.     gNewWindowHeight = gScreenHeight
  365.                         - (maxWindowsInDiagonal - 1) * kHorizontalStagger;
  366. }
  367.  
  368.  
  369. //
  370. //    This routine is called once for each window. Its function is to calculate
  371. //    the size and location for each window. First, it determines which diagonal
  372. //    the specified window should be in and its position within that diagonal.
  373. //    Then it determines the horizontal position for the window; as each window
  374. //    is always moved to the right from the previous window by a constant
  375. //    amount, the horizontal position is a simple function of the window’s
  376. //    creation rank. Next, we determine the vertical position of the window.
  377. //    This, too, is a simple function, this time of the window’s position in the
  378. //    diagonal it will be placed in. What this means is that the 1st, 9th, 17th,
  379. //    etc., windows will all have the same vertical position. Similarly for the
  380. //    2nd, 10th, 18th, etc., windows, and so on.
  381. //
  382. //    Next, we have to determine the size of the window. First, we consider the
  383. //    width. Each window in a diagonal is the same size. This size is determined
  384. //    by taking the initial window size calculated in StackSetup, and
  385. //    subtracting an amount that is a function of the diagonal number we are
  386. //    working on. First, we subtract an amount such that the right edge of each
  387. //    window in this diagonal will line up with the corresponding window in the
  388. //    previous diagonal. Next, we add back 10 pixels just to give a nice
  389. //    staggering effect on the right hand side as well as the left. After doing
  390. //    that, we work on figuring the height of the window. We start off with the
  391. //    standard height calculated in StackSetup. Then we subtract a multiple of
  392. //    10 pixels that is based on how far down in the diagonal the window is.
  393. //    This means that the height of the window is a function of the window’s
  394. //    position in the diagonal, while the width of the window is a function of
  395. //    the position of the diagonal itself.
  396. //
  397. void        StackTheWindow(WindowPtr theWindow)
  398. {
  399.     Rect    newBounds;
  400.     short    placeInDiagonal, diagonal;
  401.  
  402.     placeInDiagonal = (gWindowNumber - 1) % kWindowsPerDiagonal;
  403.     diagonal = (gWindowNumber - 1) / kWindowsPerDiagonal;
  404.  
  405.     newBounds.left        = gScreenRect.left + (gWindowNumber - 1) * kHorizontalStagger;
  406.     newBounds.top        = gScreenRect.top + placeInDiagonal * kVerticalStagger;
  407.     newBounds.right        = newBounds.left + gNewWindowWidth - diagonal * (kWindowsPerDiagonal * kHorizontalStagger - 10);
  408.     newBounds.bottom    = newBounds.top + gNewWindowHeight - placeInDiagonal * 10;
  409.  
  410.     SetWindowBounds(theWindow, newBounds);
  411. }
  412.  
  413.  
  414. /*******************************************************************************
  415.  
  416.     DoSelectFromWindowsMenu
  417.  
  418.     Called when the user selects one of the menu items containing a window’s
  419.     name (in the Windows menu). We stash off the menu item that was selected,
  420.     and then start iterating over all the windows in chronological order.
  421.     Since the list of window names in the Windows menu is also maintained in
  422.     chronological order, we know that there is a direct relationship between
  423.     the menu item number and the window’s chronological rank. In other words,
  424.     if we selected the Xth item in the Windows menu (not including the Tile or
  425.     Stack menu items), all we have to do is find the Xth oldest window and
  426.     select it.
  427.  
  428. *******************************************************************************/
  429. void    DoSelectFromWindowsMenu(short menuItem)
  430. {
  431.     gMenuItem = menuItem - iFirstWindow + 1;
  432.     ForEachWindowDo(NIL, LookForSelectedWindow, NIL);
  433. }
  434.  
  435.  
  436. void    LookForSelectedWindow(WindowPtr theWindow)
  437. {
  438.     if (--gMenuItem == 0)
  439.         SelectWindow(theWindow);
  440. }
  441.  
  442.  
  443. /*******************************************************************************
  444.  
  445.     AddWindowToMenu
  446.  
  447.     Called to add a newly created window to the Windows menu. We make that
  448.     addition by making a call to AppendMenu to create the new menu item, and
  449.     then SetItem to set that item’s name. The reason why we don’t let
  450.     AppendMenu set the name is because of the meta-character processing that
  451.     AppendMenu performs. If our window’s name happened to have something like
  452.     a “!” or “/” it it, what appeared in the menu wouldn’t be what we
  453.     expected. SetItem doesn’t do recognize the meta-characters, so we avoid
  454.     that problem when calling it.
  455.  
  456.     Next, we insert our window into our own private window list. Normally, you
  457.     don’t need to do this. The Window Manager keeps track of all the windows
  458.     on the screen so that you don’t have to. However, the list it manages
  459.     links the windows in front to back order. In other words, the frontmost
  460.     window is at the front of the list, the record for the window behind it is
  461.     second in the list, and so on.
  462.  
  463.     In order to manage our Windows menu, we’d really like to have a list of
  464.     windows in chronological order. We do this by using the refCon field of
  465.     the window record. Each window’s refCon field will point to the next
  466.     youngest window. The oldest window (the head of the chain) will be kept in
  467.     the global variable gFirstWindow. The refCon field of the last window is
  468.     NIL. If there are no windows, gFirstWindow is NIL.
  469.  
  470. *******************************************************************************/
  471. void    AddWindowToMenu(WindowPtr theWindow)
  472. {
  473.     Str255        title;
  474.     MenuHandle    windowsMenu;
  475.     WindowPtr    lastWindow;
  476.  
  477.     GetWTitle(theWindow, title);
  478.     windowsMenu = GetMenu(mWindows);
  479.     AppendMenu(windowsMenu, "\pNeed something here or call doesn’t work.");
  480.     SetItem(windowsMenu, CountMItems(windowsMenu), title);
  481.  
  482.     lastWindow = GetPreviouslyCreatedWindow(NIL);    /* NIL means “get last window” */
  483.     if (lastWindow != NIL)
  484.         SetWRefCon(lastWindow, (long) theWindow);
  485.     else
  486.         gFirstWindow = theWindow;
  487. }
  488.  
  489.  
  490. /*******************************************************************************
  491.  
  492.     RemoveWindowFromMenu
  493.  
  494.     Get the menu item that corresponds to this window, and remove that menu
  495.     item from the Windows menu. Remove the window from our chronological list
  496.     of windows.
  497.  
  498. *******************************************************************************/
  499. void    RemoveWindowFromMenu(WindowPtr theWindow)
  500. {
  501.     MenuHandle    windowsMenu;
  502.     WindowPtr    previousWindow;
  503.  
  504.     windowsMenu = GetMenu(mWindows);
  505.     DelMenuItem(windowsMenu, GetMenuItemForWindow(theWindow));
  506.  
  507.     if (theWindow == gFirstWindow) {
  508.         gFirstWindow = (WindowPtr) GetWRefCon(theWindow);
  509.     } else {
  510.         previousWindow = GetPreviouslyCreatedWindow(theWindow);
  511.         SetWRefCon(previousWindow, GetWRefCon(theWindow));
  512.     }
  513.  
  514.     SetWRefCon(theWindow, 0);
  515. }
  516.  
  517.  
  518. /*******************************************************************************
  519.  
  520.     ForEachWindowDo
  521.  
  522.     Routine that iterates over all of the windows. It first counts up the
  523.     number of windows on the monitor we are currently examining. Next, it
  524.     calls a setup routine provided by the caller. After that, it calls an
  525.     action procedure for each window. This action procedure is passed a
  526.     pointer to the current window we are iterating over. It is also able to
  527.     access the total number of windows and the window’s rank through the
  528.     global variables gNumberOfWindows and gWindowNumber. After all windows
  529.     have been acted upon, a clean up routine provided by the caller is called.
  530.  
  531.     Note that windows are iterated in chronological order, as maintained by
  532.     the links stored in the their refCon fields.
  533.  
  534. *******************************************************************************/
  535. void    ForEachWindowDo(SetUpProc theStarter,
  536.                         WindowActionProc theDoer,
  537.                         FinishUpProc enderWiggin)
  538.  
  539. {
  540.     WindowPtr    currentWindow;
  541.     Boolean        haveAWindow;
  542.     Boolean        windowIsOurConcern;
  543.  
  544.     // To get the number of windows, we recursively call ourself
  545.     // with an action procedure that simply increments a counter.
  546.     // So that we don’t infinitely recurse, we check the action
  547.     // procedure to see if it’s our counting routine. If not,
  548.     // we don’t recurse.
  549.  
  550.     if (theDoer != CountWindows) {
  551.         gNumberOfWindows = 0;
  552.         ForEachWindowDo(NIL, CountWindows, NIL);
  553.     }
  554.  
  555.     if ((theDoer == CountWindows) || (gNumberOfWindows > 0)) {
  556.     
  557.         // Start keeping track of window rank
  558.     
  559.         gWindowNumber = 0;
  560.     
  561.         // If the caller provided a setup routine, call it.
  562.     
  563.         if (theStarter != NIL)
  564.             (*theStarter)();
  565.     
  566.         // Start iterating over all the windows. Skip over any DA’s or
  567.         // dialog windows. The expression that sets “windowIsOurConcern”
  568.         // could be modified to also skip over invisible windows.
  569.     
  570.         if (theDoer != NIL) {
  571.             currentWindow = gFirstWindow;
  572.             while (currentWindow != NIL) {
  573.                 windowIsOurConcern = IsAppWindow(currentWindow);
  574.                 if (windowIsOurConcern) {
  575.                     ++gWindowNumber;
  576.                     (*theDoer)(currentWindow);
  577.                 }
  578.                 currentWindow = (WindowPtr) GetWRefCon(currentWindow);
  579.             }
  580.         }
  581.     
  582.         // Done with all the windows. If the caller
  583.         // provided a cleanup routine, call it.
  584.     
  585.         if (enderWiggin != NIL)
  586.             (*enderWiggin)();
  587.     }
  588. }
  589.  
  590.  
  591. /*******************************************************************************
  592.  
  593.     ForEachWindowPerScreenDo
  594.  
  595.     Ugly-ass routine that iterates over all of the windows, but in a peculiar
  596.     fashion. What it does is first iterate over all the monitors hooked up to
  597.     the machine. For each monitor, it first counts up the number of windows on
  598.     the monitor we are currently examining. Next, it calls a setup routine
  599.     provided by the caller. After that, it calls an action procedure for each
  600.     window on the monitor. This action procedure is passed a pointer to the
  601.     current window we are iterating over. It is also able to access the handle
  602.     to the current monitor, the size of the current monitor, the number of
  603.     windows on the current monitor, and the window’s rank on the current
  604.     monitor through the global variables gScreenDevice, gScreenRect,
  605.     gNumberOfWindows, and gWindowNumber. After all windows on the monitor have
  606.     been acted upon, a clean up routine provided by the caller is called.
  607.     Finally, we move on to the next monitor.
  608.  
  609.     Note that windows are iterated in chronological order, as maintained by
  610.     the links stored in the their refCon fields.
  611.  
  612. *******************************************************************************/
  613. void    ForEachWindowPerScreenDo(SetUpProc theStarter,
  614.                                     WindowActionProc theDoer,
  615.                                     FinishUpProc enderWiggin)
  616.  
  617. {
  618.     WindowPtr    currentWindow;
  619.     Boolean        haveAWindow;
  620.     Boolean        windowIsOurConcern;
  621.     GDHandle    currentScreenDevice;
  622.  
  623.     if (gMac.hasColorQD)
  624.         currentScreenDevice = GetDeviceList();
  625.     else
  626.         currentScreenDevice = NIL;
  627.  
  628.     do {
  629.  
  630.         // To get the number of windows on the current monitor of
  631.         // interest, we recursively call ourself with an action
  632.         // procedure that simply increments a counter. So that we
  633.         // don’t infinitely recurse, we check the action procedure
  634.         // to see if it’s our counting routine. If not, we don’t recurse.
  635.  
  636.         if (theDoer != CountWindows) {
  637.             gNumberOfWindows = 0;
  638.             ForEachWindowPerScreenDo(NIL, CountWindows, NIL);
  639.         }
  640.  
  641.         if ((theDoer == CountWindows) || (gNumberOfWindows > 0)) {
  642.  
  643.             // Start keeping track of window rank on this monitor
  644.     
  645.             gWindowNumber = 0;
  646.     
  647.             // Export the handle to the current device
  648.     
  649.             gScreenDevice = currentScreenDevice;
  650.     
  651.             // Export the size of the monitor. If we have a valid device
  652.             // handle, use it to get the device’s size. If we don’t have
  653.             // a valid handle (it is NIL -- indicating that we are running
  654.             // on a machine without Color QuickDraw), get the size of the
  655.             // screen from GetMainScreenRect() (which will effectively
  656.             // return qd.screenBits.bounds in this case).
  657.     
  658.             if (currentScreenDevice != NIL) {
  659.                 gScreenRect = (**currentScreenDevice).gdRect;
  660.                 if (currentScreenDevice = GetMainDevice())
  661.                     gScreenRect.top += GetMBarHeight();
  662.             } else {
  663.                 gScreenRect = GetMainScreenRect();
  664.                 gScreenRect.top += GetMBarHeight();
  665.             }
  666.     
  667.             // If the caller provided a setup routine, call it.
  668.     
  669.             if (theStarter != NIL)
  670.                 (*theStarter)();
  671.     
  672.             // Start iterating over all the windows. If the majority of the
  673.             // windows is on the monitor we are currently looking at from
  674.             // our outer loop, pass that window to the caller’s action
  675.             // procedure. Skip over any DA’s or dialog windows. The expression
  676.             // that sets “windowIsOurConcern” could be modified to also skip
  677.             // over invisible windows.
  678.     
  679.             if (theDoer != NIL) {
  680.                 currentWindow = gFirstWindow;
  681.                 while (currentWindow != NIL) {
  682.                     windowIsOurConcern = IsAppWindow(currentWindow) &&
  683.                             (currentScreenDevice == GetWindowDevice(currentWindow));
  684.                     if (windowIsOurConcern) {
  685.                         ++gWindowNumber;
  686.                         (*theDoer)(currentWindow);
  687.                     }
  688.                     currentWindow = (WindowPtr) GetWRefCon(currentWindow);
  689.                 }
  690.             }
  691.     
  692.             // Done with all the windows on this monitor. If the caller
  693.             // provided a cleanup routine, call it.
  694.     
  695.             if (enderWiggin != NIL)
  696.                 (*enderWiggin)();
  697.         }
  698.  
  699.         // Do the next monitor
  700.  
  701.         if (gMac.hasColorQD)
  702.             currentScreenDevice = GetNextDevice(currentScreenDevice);
  703.  
  704.     } while (currentScreenDevice != NIL);
  705. }
  706.  
  707.  
  708. /*******************************************************************************
  709.  
  710.     CountWindows
  711.  
  712.     This is a WindowActionProc called by ForEachWindowPerScreenDo and
  713.     ForEachWindowDo to count windows.
  714.  
  715. *******************************************************************************/
  716. void    CountWindows(WindowPtr theWindow)
  717. {
  718.     ++gNumberOfWindows;
  719. }
  720.  
  721.  
  722. /*******************************************************************************
  723.  
  724.     GetPreviouslyCreatedWindow
  725.  
  726.     Called to return the window immediately before the target window in our
  727.     chronological list of windows. This is handy for searching backwards when
  728.     working with a singly linked list of records.
  729.  
  730. *******************************************************************************/
  731. WindowPtr    GetPreviouslyCreatedWindow(WindowPtr theWindow)
  732. {
  733.     gFoundWindow = NIL;
  734.     gTargetWindow = theWindow;
  735.     ForEachWindowDo(NIL, LookForPreviousWindow, NIL);
  736.     return gFoundWindow;
  737. }
  738.  
  739. void    LookForPreviousWindow(WindowPtr theWindow)
  740. {
  741.     if (gTargetWindow == (WindowPtr) GetWRefCon(theWindow))
  742.         gFoundWindow = theWindow;
  743. }
  744.  
  745. /*******************************************************************************
  746.  
  747.     GetMenuItemForWindow
  748.  
  749.     Given a window pointer, return the number for the Windows menu item that
  750.     corresponds to it. This is done by iterating over all our windows in
  751.     chronological order, incrementing a counter as we go. When we get to the
  752.     window we are interested in, we stop counting. This means that our counter
  753.     ends up hold the chronological rank of our window. This rank is then added
  754.     to iFirstWindow to give us the appropriate menu item number.
  755.  
  756. *******************************************************************************/
  757.  
  758. short    GetMenuItemForWindow(WindowPtr theWindow)
  759. {
  760.     gDoneCounting = FALSE;
  761.     gTargetWindow = theWindow;
  762.     gMenuItem = iFirstWindow - 1;
  763.     ForEachWindowDo(NIL, CountSomeWindow, NIL);
  764.  
  765.     return gMenuItem;
  766. }
  767.  
  768.  
  769. void    CountSomeWindow(WindowPtr theWindow)
  770. {
  771.     if (!gDoneCounting) {
  772.         ++gMenuItem;
  773.         if (theWindow == gTargetWindow)
  774.             gDoneCounting = TRUE;
  775.     }
  776. }
  777.  
  778.  
  779. /*******************************************************************************
  780.  
  781.     SetWindowBounds
  782.  
  783.     Set the size and location of the given window’s FRAME. Note that this
  784.     differs from a simple MoveWindow/SizeWindow combination in that the
  785.     parameters passed to those routines work on the window’s content
  786.     rectangle, not the outer frame rectangle.
  787.  
  788.     What we do to accomplish this is to take the frame rectangle passed into
  789.     this routine and calculate what the content rectangle would be for a
  790.     window that had that frame’s size. This is done by looking at the
  791.     window’s current strucRgn (the region that describes the window’s frame)
  792.     and contRgn (the region that describes the inside content area of the
  793.     window). The difference in size between these two regions is determined,
  794.     and is then applied to “newBounds”. This gives us a rectangle that can be
  795.     passed to MoveWindow and SizeWindow.
  796.  
  797.     Note that we first hide the window before changing its bounds. This is so
  798.     that we don’t first see the effect of MoveWindow, and then the effect of
  799.     SizeWindow.
  800.  
  801. *******************************************************************************/
  802. void        SetWindowBounds(WindowPtr theWindow, Rect newBounds)
  803. {
  804.     short        top;
  805.     short        left;
  806.     short        height;
  807.     short        width;
  808.  
  809.     short        topInset;
  810.     short        leftInset;
  811.     short        bottomInset;
  812.     short        rightInset;
  813.  
  814.     Rect        oldBounds;
  815.  
  816.     RgnHandle    contRgn;
  817.     RgnHandle    structRgn;
  818.  
  819.     contRgn = ((WindowPeek) theWindow)->contRgn;
  820.     structRgn = ((WindowPeek) theWindow)->strucRgn;
  821.  
  822.     oldBounds = (**structRgn).rgnBBox;
  823.  
  824.     if (!EqualRect(&oldBounds, &newBounds)) {
  825.  
  826.         topInset    = (**contRgn).rgnBBox.top        - (**structRgn).rgnBBox.top;
  827.         leftInset    = (**contRgn).rgnBBox.left        - (**structRgn).rgnBBox.left;
  828.         bottomInset    = (**structRgn).rgnBBox.bottom    - (**contRgn).rgnBBox.bottom;
  829.         rightInset    = (**structRgn).rgnBBox.right    - (**contRgn).rgnBBox.right;
  830.  
  831.         top = newBounds.top + topInset;
  832.         left = newBounds.left + leftInset;
  833.         height = newBounds.bottom - top - bottomInset;
  834.         width = newBounds.right - left - rightInset;
  835.  
  836.         HideWindow(theWindow);
  837.         MoveWindow(theWindow, left, top, FALSE);
  838.         SizeWindow(theWindow, width, height, TRUE);
  839.         ZoomRect(oldBounds, newBounds, kLinear);
  840.         SelectWindow(theWindow);
  841.         ShowWindow(theWindow);
  842.     }
  843. }
  844.  
  845.  
  846. /*******************************************************************************
  847.  
  848.     GetWindowContentRect
  849.  
  850.     Given a window pointer, return the global rectangle that encloses the
  851.     content area of the window.
  852.  
  853. *******************************************************************************/
  854. Rect    GetWindowContentRect(WindowPtr window)
  855. {
  856.     WindowPtr    oldPort;
  857.     Rect        contentRect;
  858.  
  859.     GetPort(&oldPort);
  860.     SetPort(window);
  861.     contentRect = window->portRect;
  862.     LocalToGlobalRect(&contentRect);
  863.     SetPort(oldPort);
  864.     return contentRect;
  865. }
  866.  
  867.  
  868. /*******************************************************************************
  869.  
  870.     GetWindowStructureRect
  871.  
  872.     This procedure is used to get the rectangle that surrounds the entire
  873.     structure of a window. This works whether or not the window is visible. If
  874.     the window is visible, it is a simple matter of using the bounding
  875.     rectangle of the structure region. If the window is invisible, the
  876.     strucRgn is not valid. To make it valid, the window has to be moved way
  877.     off the screen and then made visible. This generates a valid strucRgn,
  878.     although it is valid for the position that is way off the screen. It still
  879.     needs to be offset back into the original position. Once the bounding
  880.     rectangle for the strucRgn is obtained, the window can then be hidden
  881.     again and moved back to its correct location. Note that ShowHide is used,
  882.     instead of ShowWindow and HideWindow. HideWindow can change the plane of
  883.     the window. Also, ShowHide does not affect the hiliting of windows.
  884.  
  885. *******************************************************************************/
  886. Rect    GetWindowStructureRect(WindowPtr window)
  887. {
  888.     const short    kOffscreenLocation = 0x4000;    /* Halfway to infinity */
  889.  
  890.     GrafPtr        oldPort;
  891.     Rect        structureRect;
  892.     Point        windowLoc;
  893.  
  894.     if (((WindowPeek)window)->visible)
  895.         structureRect = (*(((WindowPeek)window)->strucRgn))->rgnBBox;
  896.     else {
  897.         GetPort(&oldPort);
  898.         SetPort(window);
  899.         windowLoc = GetGlobalTopLeft(window);
  900.         MoveWindow(window, windowLoc.h, kOffscreenLocation, FALSE);
  901.         ShowHide(window, TRUE);
  902.         structureRect = (*(((WindowPeek) window)->strucRgn))->rgnBBox;
  903.         ShowHide(window, FALSE);
  904.         MoveWindow(window, windowLoc.h, windowLoc.v, FALSE);
  905.         OffsetRect(&structureRect, 0, windowLoc.v - kOffscreenLocation);
  906.         SetPort(oldPort);
  907.     }
  908.     return structureRect;
  909. }
  910.  
  911.  
  912. /*******************************************************************************
  913.  
  914.     GetWindowDeviceRectNMB (No Menu Bar)
  915.  
  916.     Given a window pointer, find the device that contains most of the window
  917.     and return that device’s bounding rectangle. If this device is the main
  918.     device, remove the menubar area from the rectangle.
  919.  
  920. *******************************************************************************/
  921. Rect    GetWindowDeviceRectNMB(WindowPtr window)
  922. {
  923.     Rect        deviceRect, tempRect;
  924.  
  925.     deviceRect = GetWindowDeviceRect(window);
  926.     tempRect = GetMainScreenRect();
  927.     if (EqualRect(&deviceRect, &tempRect))
  928.         deviceRect.top += GetMBarHeight();
  929.  
  930.     return deviceRect;
  931. }
  932.  
  933.  
  934. /*******************************************************************************
  935.  
  936.     GetWindowDeviceRect
  937.  
  938.     Given a window pointer, find the device that contains most of the window
  939.     and return that device’s bounding rectangle.
  940.  
  941. *******************************************************************************/
  942. Rect    GetWindowDeviceRect(WindowPtr window)
  943. {
  944.     if (gMac.hasColorQD)
  945.         return (*GetWindowDevice(window))->gdRect;
  946.     else
  947.         return GetMainScreenRect();
  948. }
  949.  
  950.  
  951. /*******************************************************************************
  952.  
  953.     GetWindowDevice
  954.  
  955.     Given a window pointer, find the device that contains most of the window
  956.     and return its handle.
  957.  
  958. *******************************************************************************/
  959. GDHandle    GetWindowDevice(WindowPtr window)
  960. {
  961.     return GetRectDevice(GetWindowStructureRect(window));
  962. }
  963.  
  964.  
  965. /*******************************************************************************
  966.  
  967.     GetRectDevice
  968.  
  969.     Given a rectangle in global coordinates, find the monitor it overlaps the
  970.     most. This is done by iterating over all of the monitors with the
  971.     GetDeviceList and GetNextDevice calls. For each device, we find the area
  972.     of overlap with the given rectangle. The device that results in the
  973.     greatest overlap is returned to the caller.
  974.  
  975.     This routine assumes the existence of the Graphics Device Manager.
  976.  
  977. *******************************************************************************/
  978. GDHandle    GetRectDevice(Rect globalRect)
  979. {
  980.     long        area;
  981.     long        maxArea;
  982.     GDHandle    device;
  983.     GDHandle    deviceToReturn;
  984.     Rect        intersection;
  985.  
  986.     deviceToReturn = GetMainDevice();            /* Use as default choice. */
  987.     maxArea = 0;
  988.  
  989.     for (device = GetDeviceList(); device != NIL; device = GetNextDevice(device)) {
  990.         if (TestDeviceAttribute(device, screenDevice)
  991.           && TestDeviceAttribute(device, screenActive)
  992.           && SectRect(&globalRect, &((*device)->gdRect), &intersection)) {
  993.             area = (intersection.right - intersection.left) *
  994.                     (intersection.bottom - intersection.top);
  995.             if (area > maxArea) {
  996.                 deviceToReturn = device;
  997.                 maxArea = area;
  998.             }
  999.         }
  1000.     }
  1001.     return deviceToReturn;
  1002. }
  1003.  
  1004.  
  1005. /*******************************************************************************
  1006.  
  1007.     LocalToGlobalRect
  1008.  
  1009.     Convert a rectangle from local coordinates to global coordinates. Like
  1010.     QuickDraw’s LocalToGlobal, it assumes that the current port is set
  1011.     correctly.
  1012.  
  1013. *******************************************************************************/
  1014. void    LocalToGlobalRect(Rect *aRect)
  1015. {
  1016.     LocalToGlobal(&topLeft(*aRect));
  1017.     LocalToGlobal(&botRight(*aRect));
  1018. }
  1019.  
  1020.  
  1021. /*******************************************************************************
  1022.  
  1023.     GetGlobalTopLeft
  1024.  
  1025.     Return the top left point of the given window’s port in global
  1026.     coordinates. This returns the top left point of the window’s content area
  1027.     only; it doesn’t include the window’s drag region (or title bar).
  1028.  
  1029. *******************************************************************************/
  1030. Point    GetGlobalTopLeft(WindowPtr window)
  1031. {
  1032.     GrafPtr            oldPort;
  1033.     Point            globalPt;
  1034.  
  1035.     GetPort(&oldPort);
  1036.     SetPort(window);
  1037.     globalPt = topLeft(window->portRect);
  1038.     LocalToGlobal(&globalPt);
  1039.     SetPort(oldPort);
  1040.     return globalPt;
  1041. }
  1042.  
  1043.  
  1044. /*******************************************************************************
  1045.  
  1046.     GetMainScreenRect
  1047.  
  1048.     Gets the bounding rectangle of the main screen (the one with the menu bar
  1049.     on it). This rectangle includes the area that contains the menubar. For
  1050.     example, on a Mac Classic, this routine should return (0, 0, 512, 342).
  1051.  
  1052. *******************************************************************************/
  1053. Rect    GetMainScreenRect(void)
  1054. {
  1055.     GDHandle    mainDevice;
  1056.     GrafPtr        mainPort;
  1057.  
  1058.     if (gMac.hasColorQD) {
  1059.         mainDevice = GetMainDevice();
  1060.         return (*mainDevice)->gdRect;
  1061.     } else {
  1062.         GetWMgrPort(&mainPort);
  1063.         return mainPort->portRect;
  1064.     }
  1065. }
  1066.  
  1067.  
  1068. /*******************************************************************************
  1069.  
  1070.      CatenatePStrings
  1071.  
  1072.     Catenate two Pascal strings by attaching the second string on the end of
  1073.     the first string. If you are running under MPW 3.2 or later, you can
  1074.     simply use the PLCatenate library routine instead.
  1075.  
  1076. *******************************************************************************/
  1077. void    CatenatePStrings(Str255 targetStr, Str255 appendStr)
  1078. {
  1079.     short appendLen;
  1080.  
  1081.     /* Figure out number of bytes to copy, up to 255 */
  1082.  
  1083.     appendLen = MIN(appendStr[0], 255 - targetStr[0]);
  1084.     if (appendLen > 0) {
  1085.         BlockMove(appendStr+1, targetStr+targetStr[0]+1, (long) appendLen);
  1086.         targetStr[0] += appendLen;
  1087.     }
  1088. }
  1089.